﻿using Orleans;
using Orleans.Concurrency;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace Puppy.CommonService
{
    [Reentrant]
    public class LockActor : Grain, ILockActor
    {
        private readonly ConcurrentQueue<(TaskCompletionSource<bool> taskCompletionSource, int maxMillisecondsHold)> queue = new ConcurrentQueue<(TaskCompletionSource<bool> taskCompletionSource, int maxMillisecondsHold)>();
        private int locked;
        private long holdTimestamp;

        public async Task<bool> LockAsync(int millisecondsDelay = 0, int maxSecondsHold = 30)
        {
            var nowTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            // 已锁住但是超过期时间，解锁
            if (locked == 1 && nowTimestamp > holdTimestamp)
            {
                Interlocked.CompareExchange(ref locked, 0, 1);
            }

            // 锁未占用，加锁并重置过期时间戳
            if(Interlocked.CompareExchange(ref locked, 1, 0) == 0)
            {
                holdTimestamp = nowTimestamp + maxSecondsHold;
                return true;
            }
            else
            {
                var taskSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
                if(millisecondsDelay != 0)
                {
                    using var tc = new CancellationTokenSource(millisecondsDelay);
                    tc.Token.Register(() =>
                    {
                        taskSource.TrySetResult(false);
                    });
                }

                queue.Enqueue((taskSource, maxSecondsHold));
                return await taskSource.Task;
            }
        }

        public Task UnlockAsync()
        {
            if(queue.TryDequeue(out var item))
            {
                if (!item.taskCompletionSource.Task.IsCompleted)
                {
                    if (item.taskCompletionSource.TrySetResult(true))
                    {
                        holdTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + item.maxMillisecondsHold;
                    }
                }
                else
                {
                    return UnlockAsync();
                }
            }
            else
            {
                Interlocked.CompareExchange(ref locked, 0, 1);
            }

            return Task.CompletedTask;
        }
    }
}
